package org.checkerframework.dataflow.analysis;

import java.util.Map;
import java.util.StringJoiner;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.plumelib.util.StringsPlume;

/**
 * Implementation of a {@link TransferResult} with just one non-exceptional store. The result of
 * {@code getThenStore} and {@code getElseStore} is equal to the only underlying store.
 *
 * @param <V> type of the abstract value that is tracked
 * @param <S> the store type used in the analysis
 */
public class RegularTransferResult<V extends AbstractValue<V>, S extends Store<S>>
    extends TransferResult<V, S> {

  /** The regular result store. */
  protected final S store;

  /**
   * True if the store changed; see {@link
   * org.checkerframework.dataflow.analysis.TransferResult#storeChanged}.
   */
  private final boolean storeChanged;

  /**
   * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code
   * null} for {@link org.checkerframework.dataflow.analysis.TransferResult#exceptionalStores}.
   *
   * <p><em>Exceptions</em>: If the corresponding {@link
   * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then it is assumed that no
   * special handling is necessary and the store before the corresponding {@link
   * org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge.
   *
   * <p><em>Aliasing</em>: {@code resultStore} is not allowed to be used anywhere outside of this
   * class (including use through aliases). Complete control over the object is transferred to this
   * class.
   *
   * @param value the abstract value produced by the transfer function
   * @param resultStore regular result store
   * @param storeChanged true if the store changed; see {@link
   *     org.checkerframework.dataflow.analysis.TransferResult#storeChanged}
   * @see #RegularTransferResult(AbstractValue, Store, Map, boolean)
   */
  public RegularTransferResult(@Nullable V value, S resultStore, boolean storeChanged) {
    this(value, resultStore, null, storeChanged);
  }

  /**
   * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code
   * null} for {@link org.checkerframework.dataflow.analysis.TransferResult#exceptionalStores} and
   * {@code false} for {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged}.
   *
   * @param value the abstract value produced by the transfer function
   * @param resultStore regular result store
   * @see #RegularTransferResult(AbstractValue, Store, Map, boolean)
   */
  public RegularTransferResult(@Nullable V value, S resultStore) {
    this(value, resultStore, false);
  }

  /**
   * Create a new {@link #RegularTransferResult(AbstractValue, Store, Map, boolean)}, using {@code
   * false} for {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged}.
   *
   * @param value the abstract value produced by the transfer function
   * @param resultStore the regular result store
   * @param exceptionalStores the stores in case the basic block throws an exception, or null if the
   *     basic block does not throw any exceptions
   * @see #RegularTransferResult(AbstractValue, Store, Map, boolean)
   */
  public RegularTransferResult(
      @Nullable V value, S resultStore, @Nullable Map<TypeMirror, S> exceptionalStores) {
    this(value, resultStore, exceptionalStores, false);
  }

  /**
   * Create a {@code TransferResult} with {@code resultStore} as the resulting store. If the
   * corresponding {@link org.checkerframework.dataflow.cfg.node.Node} is a boolean node, then
   * {@code resultStore} is used for both the 'then' and 'else' edge.
   *
   * <p><em>Exceptions</em>: If the corresponding {@link
   * org.checkerframework.dataflow.cfg.node.Node} throws an exception, then the corresponding store
   * in {@code exceptionalStores} is used. If no exception is found in {@code exceptionalStores},
   * then it is assumed that no special handling is necessary and the store before the corresponding
   * {@link org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge.
   *
   * <p><em>Aliasing</em>: {@code resultStore} and any store in {@code exceptionalStores} are not
   * allowed to be used anywhere outside of this class (including use through aliases). Complete
   * control over the objects is transferred to this class.
   *
   * @param value the abstract value produced by the transfer function
   * @param resultStore the regular result store
   * @param exceptionalStores the stores in case the basic block throws an exception, or null if the
   *     basic block does not throw any exceptions
   * @param storeChanged see {@link
   *     org.checkerframework.dataflow.analysis.TransferResult#storeChanged}
   */
  public RegularTransferResult(
      @Nullable V value,
      S resultStore,
      @Nullable Map<TypeMirror, S> exceptionalStores,
      boolean storeChanged) {
    super(value, exceptionalStores);
    this.store = resultStore;
    this.storeChanged = storeChanged;
  }

  @Override
  public S getRegularStore() {
    return store;
  }

  @Override
  public S getThenStore() {
    return store;
  }

  @Override
  public S getElseStore() {
    // copy the store such that it is the same as the result of getThenStore
    // (that is, identical according to equals), but two different objects.
    return store.copy();
  }

  @Override
  public boolean containsTwoStores() {
    return false;
  }

  @Override
  public String toString() {
    StringJoiner result = new StringJoiner(System.lineSeparator());
    result.add("RegularTransferResult(");
    result.add("  resultValue = " + StringsPlume.indentLinesExceptFirst(2, resultValue));
    // "toString().trim()" works around bug where toString ends with a newline.
    result.add(
        "  store = " + StringsPlume.indentLinesExceptFirst(2, store.toString().trim()) + ")");
    return result.toString();
  }

  /**
   * See {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged()}.
   *
   * @see org.checkerframework.dataflow.analysis.TransferResult#storeChanged()
   */
  @Override
  public boolean storeChanged() {
    return storeChanged;
  }

  @Override
  public RegularTransferResult<V, S> withExceptionalStores(Map<TypeMirror, S> exceptionalStores) {
    return new RegularTransferResult<>(resultValue, store, exceptionalStores, storeChanged);
  }
}
